using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using System.Threading;

// http://xna-uk.net/blogs/randomchaos/archive/2008/08/15/generic-xna-threading-for-windows-amp-xbox-360.aspx

namespace Alone.Handlers
{
	public interface IThreadedUpdateComponent
	{
		void ThreadUpdate(GameTime gameTime);
	}

	public interface IThreadManager	{ }
	
	/// <summary>
	/// This is a game component that implements IUpdateable.
	/// </summary>
	public class ThreadManager : Microsoft.Xna.Framework.GameComponent, IThreadManager
	{
		private Dictionary<int, GameThread> gameThreads = new Dictionary<int, GameThread>();

		/// <summary>
		/// Manages GameTime to be passed onto the threads.
		/// </summary>
		static GameTime gameTime;

		public ThreadManager(Game game)
			: base(game)
		{
			Game.Services.AddService(typeof(IThreadManager), this);
		}

		/// <summary>
		/// Allows the game component to perform any initialization it needs to before starting
		/// to run.  This is where it can query for any required services and load content.
		/// </summary>
		public override void Initialize()
		{
			base.Initialize();
		}

		/// <summary>
		/// Allows the game component to update itself.
		/// </summary>
		/// <param name="gameTime">Provides a snapshot of timing values.</param>
		public override void Update(GameTime gameTime)
		{
			ThreadManager.gameTime = gameTime;

			for (int i = 0; i < gameThreads.Count; i++)
			{
				gameThreads[i].Update(gameTime);
			}

			base.Update(gameTime);
		}

		/// <summary>
		/// Method to kill a single thread.
		/// </summary>
		/// <param name="index"></param>
		public void KillThread(int affinityIndex)
		{
			GameThread gameThread = gameThreads[affinityIndex];

			if (gameThread != null)
			{
				gameThreads[affinityIndex].KillThread();
			}
		}

		/// <summary>
		/// Method to start a thread
		/// </summary>
		/// <param name="threadCode"></param>
		/// <param name="threadInterval"></param>
		/// <param name="index"></param>
		public void StartThread(int affinityIndex)
		{
			GameThread gameThread = gameThreads[affinityIndex];

			if (gameThread.stopThread)
			{
				gameThread.thread = new Thread(gameThread.threadStart);
				gameThread.stopThread = false;
				gameThread.thread.Start();
			}
		}

		public void AddThreadComponent(int affinityIndex, IThreadedUpdateComponent component)
		{
			GameThread gameThread = gameThreads[affinityIndex];

			if (gameThread == null)
			{
				GameThread newThread = new GameThread(affinityIndex);
				gameThreads.Add(affinityIndex, newThread);
				gameThread = gameThreads[affinityIndex];
			}

			gameThread.components.Add(component);
		}
		
		
		/// <summary>
		/// Method to tidy up unfinished threads.
		/// </summary>
		/// <param name="disposing"></param>
		protected override void Dispose(bool disposing)
		{
			for (int i = 0; i < gameThreads.Count; i++)
			{
				KillThread(i);
			}

			base.Dispose(disposing);
		}
	}

	/// <summary>
	/// This is the delegate to be used for passing the code to be called in the tread.
	/// </summary>
	/// <param name="gameTime">GameTime</param>
	//public delegate void ThreadCode(GameTime gameTime);

	/// <summary>
	/// This class holds the required data for the code to be called in the thread.
	/// </summary>
	internal class GameThread
	{
		// List of components to run in this thread
		internal List<IThreadedUpdateComponent> components = new List<IThreadedUpdateComponent>();
		
		internal Thread thread;
		internal ThreadStart threadStart;
		
		/// <summary>
		/// Mutex to stop thread clashes.
		/// </summary>
		private static Mutex mutex = new Mutex();
		/// <summary>
		/// Used to make the thread wait.
		/// </summary>
		private ManualResetEvent threadStopEvent = new ManualResetEvent(false);
		/// <summary>
		/// Bool to control imediate stopping of thread loop.
		/// </summary>        
		public bool stopThread = false;
		/// <summary>
		/// Interval thread will wait befoer next cycle.
		/// </summary>
		private int threadIntervals = 10;
		private int processorAffinity;
		
		public GameTime gameTime = null;

		public GameThread(int affinityIndex)
		{
			this.processorAffinity = affinityIndex;
			this.threadStart = new ThreadStart(this.Worker);
			this.thread = new Thread(threadStart);
		}

		public void Update(GameTime gameTime)
		{
			this.gameTime = gameTime;
		}

		public void Worker()
		{
#if XBOX
            Thread.CurrentThread.SetProcessorAffinity(new int[] { processorAffinity });
#endif
			do
			{
				try
				{
					mutex.WaitOne();
					if (gameTime != null)
					{
						foreach (IThreadedUpdateComponent component in components)
						{
							component.ThreadUpdate(this.gameTime);
						}
					}
				}
				finally
				{
					mutex.ReleaseMutex();
				}
			}
			while (!threadStopEvent.WaitOne(threadIntervals, false) && !stopThread);
		}

		public void KillThread()
		{
			if (!stopThread)
			{
				mutex.WaitOne();
				stopThread = true;
				thread.Join(0);
				mutex.ReleaseMutex();
			}
		}
	}
}
